// This file is part of Notepad2.
// See License.txt for details about distribution and modification.
//! Lexer for JSON, JSON5.

#include <cassert>
#include <cstring>

#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"

#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "CharacterSet.h"
#include "LexerModule.h"

using namespace Scintilla;

namespace {

enum {
	JsonChar_None = 0,
	JsonChar_Operator = 1,
	JsonChar_OperatorOpen = 2,
	JsonChar_OperatorClose = 3,
	JsonChar_String = 4,
	JsonChar_Digit = 5,
	JsonChar_WordStart = 6,
	JsonChar_Dot = 7,
	JsonChar_Slash = 8,
	JsonChar_Char = 9,
	JsonChar_IDStart = 10,

	JsonChar_Number = 0x10,
	JsonChar_ID = 0x20,
};

void ColouriseJSONDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, LexerWordList keywordLists, Accessor &styler) {
	const bool fold = styler.GetPropertyInt("fold", 1) != 0;

	int state = initStyle;
	unsigned char chNext = styler[startPos];
	styler.StartAt(startPos);
	styler.StartSegment(startPos);
	const Sci_PositionU endPos = startPos + lengthDoc;

	Sci_Position lineCurrent = styler.GetLine(startPos);
	int levelCurrent = SC_FOLDLEVELBASE;
	if (lineCurrent > 0) {
		levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
	}
	int levelNext = levelCurrent;

	constexpr int MaxLexWordLength = 8; // Infinity
	char buf[MaxLexWordLength + 1] = "";
	int wordLen = 0;

	// JSON5 line continue
	bool lineContinue = false;
	bool atLineStart = startPos == static_cast<Sci_PositionU>(styler.LineStart(lineCurrent));
	Sci_PositionU lineStartNext = styler.LineStart(lineCurrent + 1);
	Sci_PositionU lineEndPos = ((lineStartNext < endPos) ? lineStartNext : endPos) -1;

	// scripts/GenerateCharTable.py
	static const unsigned char kJsonCharClass[256] = {
//++Autogenerated -- start of section automatically generated
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 4, 0, 42, 0, 0, 9, 0, 0, 0, 17, 1, 17, 23, 8,
53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 1, 0, 0, 0, 0, 0,
42, 58, 58, 58, 58, 58, 58, 58, 58, 54, 58, 58, 58, 58, 54, 58,
58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 2, 32, 3, 0, 58,
0, 58, 58, 58, 58, 58, 54, 58, 58, 58, 58, 58, 58, 58, 54, 58,
58, 58, 58, 58, 54, 58, 58, 58, 58, 58, 58, 2, 0, 3, 0, 0,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
//--Autogenerated -- end of section automatically generated
	};

	for (Sci_PositionU i = startPos; i < endPos; i++) {
		const unsigned char ch = chNext;
		chNext = styler.SafeGetCharAt(i + 1);

		switch (state) {
		case SCE_JSON_OPERATOR:
			styler.ColourTo(i - 1, state);
			state = SCE_JSON_DEFAULT;
			break;

		case SCE_JSON_NUMBER:
			if (!(kJsonCharClass[ch] & JsonChar_Number)) {
				styler.ColourTo(i - 1, state);
				state = SCE_JSON_DEFAULT;
			}
			break;

		case SCE_JSON_MAYBE_KEYWORD:
			if (!(kJsonCharClass[ch] & JsonChar_ID)) {
				buf[wordLen] = '\0';
				if (keywordLists[0]->InList(buf)) {
					styler.ColourTo(i - 1, SCE_JSON_KEYWORD);
				} else if (ch == ':' || chNext == ':' || LexGetNextChar(i + 1, styler) == ':') {
					styler.ColourTo(i - 1, SCE_JSON_PROPERTYNAME);
				}
				state = SCE_JSON_DEFAULT;
			} else if (wordLen < MaxLexWordLength) {
				buf[wordLen++] = static_cast<char>(ch);
			} else {
				state = SCE_JSON_IDENTIFIER;
			}
			break;

		case SCE_JSON_IDENTIFIER:
			if (!(kJsonCharClass[ch] & JsonChar_ID)) {
				if (ch == ':' || chNext == ':' || LexGetNextChar(i + 1, styler) == ':') {
					styler.ColourTo(i - 1, SCE_JSON_PROPERTYNAME);
				}
				state = SCE_JSON_DEFAULT;
			}
			break;

		case SCE_JSON_STRING:
		case SCE_JSON_CHARACTER:
			if (i == lineEndPos) { // atLineEnd
				if (lineContinue) {
					lineContinue = false;
				} else {
					styler.ColourTo(i - 1, state);
					state = SCE_JSON_DEFAULT;
				}
			} else if (ch == '\\') {
				styler.ColourTo(i - 1, state);
				if (IsEOLChar(chNext)) {
					styler.ColourTo(i, SCE_JSON_DEFAULT);
					lineContinue = true;
				} else {
					// highlight any character as escape sequence
					++i;
					if (chNext == 'u' || chNext == 'x') {
						int count = (chNext == 'u') ? 4 : 2;
						do {
							chNext = styler.SafeGetCharAt(i + 1);
							if (!IsHexDigit(chNext)) {
								break;
							}
							--count;
							++i;
						} while (count);
					}

					chNext = styler.SafeGetCharAt(i + 1);
					styler.ColourTo(i, SCE_JSON_ESCAPESEQUENCE);
					continue;
				}
			} else if ((state == SCE_JSON_STRING && ch == '\"') || (state == SCE_JSON_CHARACTER && ch == '\'')) {
				if (chNext == ':' || LexGetNextChar(i + 1, styler) == ':') {
					styler.ColourTo(i, SCE_JSON_PROPERTYNAME);
				} else {
					styler.ColourTo(i, state);
				}
				state = SCE_JSON_DEFAULT;
				continue;
			}
			break;

		case SCE_JSON_LINECOMMENT:
			if (atLineStart) {
				styler.ColourTo(i - 1, state);
				state = SCE_JSON_DEFAULT;
			}
			break;

		case SCE_JSON_BLOCKCOMMENT:
			if (ch == '*' && chNext == '/') {
				i++;
				chNext = styler.SafeGetCharAt(i + 1);
				styler.ColourTo(i, state);
				state = SCE_JSON_DEFAULT;
				levelNext--;
				continue;
			}
			break;
		}

		if (state == SCE_JSON_DEFAULT) {
			const int charClass = kJsonCharClass[ch] & 0x0F;
			switch (charClass) {
			case JsonChar_Operator:
				styler.ColourTo(i - 1, state);
				state = SCE_JSON_OPERATOR;
				break;
			case JsonChar_OperatorOpen:
				styler.ColourTo(i - 1, state);
				state = SCE_JSON_OPERATOR;
				levelNext++;
				break;
			case JsonChar_OperatorClose:
				styler.ColourTo(i - 1, state);
				state = SCE_JSON_OPERATOR;
				levelNext--;
				break;
			case JsonChar_String:
				styler.ColourTo(i - 1, state);
				state = SCE_JSON_STRING;
				break;
			case JsonChar_Digit:
				styler.ColourTo(i - 1, state);
				state = SCE_JSON_NUMBER;
				break;
			case JsonChar_WordStart:
				styler.ColourTo(i - 1, state);
				state = SCE_JSON_MAYBE_KEYWORD;
				buf[0] = static_cast<char>(ch);
				wordLen = 1;
				break;
			case JsonChar_Dot:
				styler.ColourTo(i - 1, state);
				state = IsADigit(chNext) ? SCE_JSON_NUMBER : SCE_JSON_OPERATOR;
				break;
			case JsonChar_Slash:
				if (chNext == '/') {
					styler.ColourTo(i - 1, state);
					state = SCE_JSON_LINECOMMENT;
				} else if (chNext == '*') {
					styler.ColourTo(i - 1, state);
					state = SCE_JSON_BLOCKCOMMENT;
					levelNext++;
					i++;
					chNext = styler.SafeGetCharAt(i + 1);
				}
				break;
			case JsonChar_Char:
				styler.ColourTo(i - 1, state);
				state = SCE_JSON_CHARACTER;
				break;
			case JsonChar_IDStart:
				styler.ColourTo(i - 1, state);
				state = SCE_JSON_IDENTIFIER;
				break;
			}
		}

		if (styler.IsLeadByte(ch)) {
			// ignore trail byte in DBCS character
			i++;
			chNext = styler.SafeGetCharAt(i + 1);
		}

		atLineStart = i == lineEndPos;
		if (atLineStart) {
			if (fold) {
				const int levelUse = levelCurrent;
				int lev = levelUse | levelNext << 16;
				if (levelUse < levelNext) {
					lev |= SC_FOLDLEVELHEADERFLAG;
				}
				if (lev != styler.LevelAt(lineCurrent)) {
					styler.SetLevel(lineCurrent, lev);
				}
				levelCurrent = levelNext;
			}
			lineCurrent++;
			lineStartNext = styler.LineStart(lineCurrent + 1);
			lineEndPos = ((lineStartNext < endPos) ? lineStartNext : endPos) -1;
		}
	}

	// Colourise remaining document
	styler.ColourTo(endPos - 1, state);
}

}

LexerModule lmJSON(SCLEX_JSON, ColouriseJSONDoc, "json");
